{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 音频片段分析模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from music21 import *\n",
    "import numpy\n",
    "import sys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析melody的波动性，使其尽可能地保持在同一个音阶上\n",
    "def analyzeMelodicMotion(melody):\n",
    "    prevMidi = None\n",
    "    totalDistance = 0.0\n",
    "    totalNotes = 0.1 # 避免分母为0\n",
    "    for note in melody:\n",
    "        if note[0] == -1: continue\n",
    "        totalNotes += 1.0\n",
    "        thisMidi = note[0]\n",
    "        if prevMidi is not None:\n",
    "            totalDistance += abs(thisMidi - prevMidi)#计算相邻音符间的绝对值\n",
    "        prevMidi = thisMidi\n",
    "    # 58 避免出现负数的情况\n",
    "    return 1 - (totalDistance / (58 * totalNotes))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析harmony中和弦内部的分数，评价一个和弦的好坏\n",
    "INTERVAL_SCORES = [0.5, 0.25, 0.25, 1, 0.75, 0.5, 0.25, 1, 0.5, 0.5, 0.75, 0.75]#音程分数\n",
    "def analyzeHarmonicConsonance(harmony):\n",
    "    cumulativeScore = 0.0\n",
    "    totalIntervals = 0.1\n",
    "    for chord in harmony:\n",
    "        if chord[0] == -1: continue\n",
    "        note1 = chord[0][0]\n",
    "        for note2 in chord[0]:\n",
    "            if note1 is note2: continue\n",
    "            totalIntervals += 1\n",
    "            thisInterval = abs(note1 - note2) % 12#计算相邻音符所处音调的绝对值\n",
    "            cumulativeScore += INTERVAL_SCORES[thisInterval]\n",
    "\n",
    "    return cumulativeScore / totalIntervals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "#分析harmony中出现音程最多的次数，\n",
    "def analyzeHarmonicConsistency(harmony):\n",
    "    structures = [[0 for i in range(12)] for j in range(12)]\n",
    "    totalStructures = 0.01\n",
    "\n",
    "    for chord in harmony:\n",
    "        if chord[0] == -1: continue\n",
    "        interval1 = abs(chord[0][0] - chord[0][1]) % 12\n",
    "        interval2 = abs(chord[0][0] - chord[0][2]) % 12\n",
    "        if interval1 > interval2:\n",
    "            structures[interval1][interval2] += 1.0\n",
    "        else:\n",
    "            structures[interval2][interval1] += 1.0\n",
    "        totalStructures += 1\n",
    "\n",
    "    mostCommon = [0.0, 0.0, 0.0, 0.0, 0.0]\n",
    "\n",
    "    for structure in structures:\n",
    "        thisMax = max(structure)\n",
    "\n",
    "        if thisMax >= mostCommon[0]:\n",
    "            ### 第0个开始右移\n",
    "            mostCommon[4] = mostCommon[3]\n",
    "            mostCommon[3] = mostCommon[2]\n",
    "            mostCommon[2] = mostCommon[1]\n",
    "            mostCommon[1] = mostCommon[0]\n",
    "            mostCommon[0] = thisMax\n",
    "        elif thisMax >= mostCommon[1]:\n",
    "            ### 第1个开始右移\n",
    "            mostCommon[4] = mostCommon[3]\n",
    "            mostCommon[3] = mostCommon[2]\n",
    "            mostCommon[2] = mostCommon[1]\n",
    "            mostCommon[1] = thisMax\n",
    "        elif thisMax >= mostCommon[2]:\n",
    "            ### 第2个开始右移\n",
    "            mostCommon[4] = mostCommon[3]\n",
    "            mostCommon[3] = mostCommon[2]\n",
    "            mostCommon[2] = thisMax\n",
    "        elif thisMax >= mostCommon[3]:\n",
    "            ### 第3个开始右移\n",
    "            mostCommon[4] = mostCommon[3]\n",
    "            mostCommon[3] = thisMax\n",
    "        elif thisMax >= mostCommon[4]:\n",
    "            ### 第4个开始右移\n",
    "            mostCommon[4] = thisMax\n",
    "\n",
    "    # mostCommon[0] > mostCommon[1] > mostCommon[2]\n",
    "    return (mostCommon[0] + mostCommon[1] + mostCommon[2]) / totalStructures"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析音频中的宏和音，宏和音使用5~8个音符的时候最好\n",
    "MACRO_SCORES = [0.0, 0.1, 0.15, 0.25, 0.5, 0.65, 0.8, 1.0, 0.8, 0.65, 0.5, 0.25, 0.0 ]\n",
    "def analyzeMacroharmony(melody, harmony):\n",
    "    notesUsed = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]#音乐片段中使用到的音调\n",
    "    for note in melody:\n",
    "        if note[0] == -1: continue\n",
    "        index = note[0] % 12\n",
    "        notesUsed[index] += 1\n",
    "    for chord in harmony:\n",
    "        if chord[0] == -1: continue\n",
    "        for note in chord[0]:\n",
    "            index = note % 12\n",
    "            notesUsed[index] += 1\n",
    "\n",
    "\n",
    "    avg = numpy.mean(notesUsed)#计算音调使用次数的平均值\n",
    "    std = numpy.std(notesUsed)#计算音调使用次数的标准差\n",
    "\n",
    "    twoStdDevs = avg - 2 * std\n",
    "    oneStdDev = avg - std\n",
    "    i = 0\n",
    "    mod = 1\n",
    "    while i < len(notesUsed):\n",
    "        if notesUsed[i] < twoStdDevs:\n",
    "            notesUsed[i] = 0\n",
    "            mod -= 0.05\n",
    "        elif notesUsed[i] < oneStdDev:\n",
    "            notesUsed[i] = 0\n",
    "            mod -= 0.10\n",
    "        i += 1\n",
    "\n",
    "    numberNotesUsed = 0\n",
    "    for value in notesUsed:\n",
    "        if value > 0: numberNotesUsed += 1\n",
    "\n",
    "    return MACRO_SCORES[numberNotesUsed] * mod"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析音频音符的中心度\n",
    "def analyzeCentricity(melody, harmony):\n",
    "    notesUsed = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]#音乐片段中使用到的音调\n",
    "    totalNotes = 0.1\n",
    "    \n",
    "    for note in melody:\n",
    "        if note[0] == -1: continue\n",
    "        index = note[0] % 12\n",
    "        notesUsed[index] += 1.0\n",
    "        totalNotes += 1.0\n",
    "    \n",
    "    for chord in harmony:\n",
    "        if chord[0] == -1: continue\n",
    "        for note in chord[0]:\n",
    "            index = note % 12\n",
    "            notesUsed[index] += 1.0\n",
    "            totalNotes += 1.0\n",
    "    \n",
    "    maxFreq = 0.1\n",
    "    secondFreq = 0.1\n",
    "    for value in notesUsed:\n",
    "        freq = value / totalNotes\n",
    "        if freq > maxFreq:\n",
    "            maxFreq = freq\n",
    "        elif freq > secondFreq:\n",
    "            secondFreq = freq\n",
    "\n",
    "    return 1 - (secondFreq / maxFreq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析音符与和弦之间的协调性\n",
    "COHESION_SCORES = [1, 0.25, 0.25, 0.75, 0.75, 0.5, 0.25, 1, 0.5, 0.5, 0.75, 1]\n",
    "def analyzeCohesion(melody, harmony):\n",
    "    cumulativeScore = 0.0\n",
    "    totalIntervals = 0.1\n",
    "    quarterLength = 0\n",
    "\n",
    "    i = 0\n",
    "    j = 0\n",
    "    while i < len(melody) and j < len(harmony):\n",
    "        melodyNote = melody[i][0]\n",
    "        chordNotes = harmony[j][0]\n",
    "\n",
    "        if chordNotes == -1:\n",
    "            j += 1\n",
    "            continue\n",
    "        if melodyNote == -1:\n",
    "            i += 1\n",
    "            continue\n",
    "\n",
    "        for note in chordNotes:\n",
    "            interval = abs(melodyNote - note) % 12\n",
    "            cumulativeScore += COHESION_SCORES[interval]\n",
    "            totalIntervals += 1\n",
    "    \n",
    "        i += 1\n",
    "        if quarterLength >= harmony[j][1]:\n",
    "            j += 1\n",
    "\n",
    "    return cumulativeScore / totalIntervals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析音频中每个音符的长度\n",
    "def analyzeNoteLength(melody, harmony):\n",
    "    durations = {}\n",
    "    totalDurations = 0.01\n",
    "\n",
    "    for note in melody:\n",
    "        if note[1] not in durations:\n",
    "            durations[note[1]] = 1\n",
    "        else:\n",
    "            durations[note[1]] += 1\n",
    "        totalDurations += 1.0\n",
    "    for chord in harmony:\n",
    "        if chord[1] not in durations:\n",
    "            durations[chord[1]] = 1\n",
    "        else:\n",
    "            durations[chord[1]] += 1\n",
    "        totalDurations += 1.0\n",
    "\n",
    "    max = 0.0\n",
    "    secondMax = 0.0\n",
    "    for key in durations:\n",
    "        if durations[key] >= max:\n",
    "            secondMax = max\n",
    "            max = durations[key]\n",
    "        elif durations[key] > secondMax:\n",
    "            secondMax = durations[key]\n",
    "\n",
    "    return (max / totalDurations) + (secondMax / totalDurations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析音频的八度\n",
    "def analyzeOctave(melody, harmony):\n",
    "    octaveSums = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n",
    "    totalNotes = 0.01\n",
    "    for note in melody:\n",
    "        if note == -1: continue\n",
    "        octave = note[0] / 12\n",
    "        octaveSums[int(octave)] += 1\n",
    "        totalNotes += 1\n",
    "\n",
    "    mostCommonMelodyOctave = 0\n",
    "    for octave in octaveSums:\n",
    "        proportion = octave / totalNotes\n",
    "        if proportion > mostCommonMelodyOctave:\n",
    "            mostCommonMelodyOctave = proportion\n",
    "\n",
    "    octaveSums = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n",
    "    totalNotes = 0.01\n",
    "    for chord in harmony:\n",
    "        if chord[0] == -1: continue\n",
    "        for note in chord[0]:\n",
    "            octave = note / 12\n",
    "            octaveSums[int(octave)] += 1\n",
    "            totalNotes += 1\n",
    "\n",
    "    mostCommonHarmonyOctave = 0\n",
    "    for octave in octaveSums:\n",
    "        proportion = octave / totalNotes\n",
    "        if proportion > mostCommonHarmonyOctave:\n",
    "            mostCommonHarmonyOctave = proportion\n",
    "\n",
    "    return (mostCommonMelodyOctave + mostCommonHarmonyOctave) / 2.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#分析和弦中出现的相同的音符\n",
    "def analyzeCommonNotes(harmony):\n",
    "    totalChords = 0.01\n",
    "    score = 0\n",
    "    # Award points for notes being the same between two chords\n",
    "    i = 0\n",
    "    while (i + 1) < len(harmony):\n",
    "        chord1 = harmony[i]\n",
    "        chord2 = harmony[i + 1]\n",
    "        # Check for rests\n",
    "        if chord1[0] == -1 or chord2[0] == -1:\n",
    "            i += 1\n",
    "            continue\n",
    "\n",
    "        untuple1 = [chord1[0][0], chord1[0][1], chord1[0][2]]\n",
    "        untuple1.sort()\n",
    "        untuple2 = [chord2[0][0], chord2[0][1], chord2[0][2]]\n",
    "        untuple2.sort()\n",
    "\n",
    "        if (untuple1[0] % 12) == (untuple2[0] % 12):\n",
    "            score += 1\n",
    "        if (untuple1[1] % 12) == (untuple2[1] % 12):\n",
    "            score += 1\n",
    "        if (untuple1[2] % 12) == (untuple2[2] % 12):\n",
    "            score += 1\n",
    "\n",
    "        i += 1\n",
    "        totalChords += 1\n",
    "\n",
    "    return score / (totalChords * 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#定义一个分析音频分数的类\n",
    "class ScoreAnalyzer:\n",
    "\n",
    "    def __init__(self, dataScore):\n",
    "        self.melody = dataScore[\"melody\"]\n",
    "        self.harmony = dataScore[\"harmony\"]\n",
    "\n",
    "    def getAnalysisScore(self):\n",
    "        motion = analyzeMelodicMotion(self.melody)\n",
    "        consonance = analyzeHarmonicConsonance(self.harmony)\n",
    "        consistency = analyzeHarmonicConsistency(self.harmony)\n",
    "        macroharmony = analyzeMacroharmony(self.melody, self.harmony)\n",
    "        centricity = analyzeCentricity(self.melody, self.harmony)\n",
    "        cohesion = analyzeCohesion(self.melody, self.harmony)\n",
    "        noteLength = analyzeNoteLength(self.melody, self.harmony)\n",
    "        octave = analyzeOctave(self.melody, self.harmony)\n",
    "        commonNotes = analyzeCommonNotes(self.harmony)\n",
    "\n",
    "        return [motion, consonance, consistency, macroharmony, centricity, cohesion, noteLength, octave, commonNotes]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:Empty]",
   "language": "python",
   "name": "conda-env-Empty-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
